﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Kokomo.Environment;
using Kokomo.Mirror.CliMetadata;
using Kokomo.Environment.Documents;
using System.Windows.Media;
using Kokomo.Mirror.CliMetadata.Tables;
using Kokomo.Mirror.CliMetadata.Signatures;
using System.Windows.Input;

namespace Kokomo.PeInspector.Reflection
{
	[ViewType(typeof(AssemblyReferencesView))]
	class AssemblyReferencesViewModel : NotifierObject
	{
		public AssemblyReferencesViewModel(
			Assembly assembly,
			ICommand navigateToCommand
			)
		{
			if (assembly == null) throw new ArgumentNullException("assembly");

			this.NavigateToCommand = navigateToCommand;

			ImageSource icon = AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(MetadataIconKeys.Assembly);
			IExtendedModuleInfo extendedModule = assembly.ManifestModule as IExtendedModuleInfo;
			if (extendedModule != null)
			{
				MetadataContainer metadata = extendedModule.Metadata;
				this._module = assembly.ManifestModule;
				this._metadata = metadata;
				int referenceCount = metadata.AssemblyRefTable.RowCount;
				SortedList<string, TreeNodeViewModel> assemblyNodes = new SortedList<string, TreeNodeViewModel>();
				for (int i = 0; i < referenceCount; i++)
				{
					AssemblyName assemblyName = metadata.AssemblyRefTable[i].ToAssemblyName(metadata);

					TreeNodeViewModel node = new TreeNodeViewModel(
						icon,
						UiString.FromString(assemblyName.FullName),
						assemblyName
						) { ItemsSourceDelegate = this.CreateItemsSourceDelegate(i) };
					assemblyNodes.Add(assemblyName.FullName, node);
				}

				this.ReferencedAssemblyNodes = assemblyNodes.Values;
			}
			else
			{
				AssemblyName[] assemblies = assembly.GetReferencedAssemblies();
				TreeNodeViewModel[] nodes = new TreeNodeViewModel[assemblies.Length];
				for (int i = 0; i < assemblies.Length; i++)
				{
					AssemblyName assemblyName = assemblies[i];
					nodes[i] = new TreeNodeViewModel(
						icon,
						UiString.FromString(assemblyName.FullName),
						assemblyName
						);
				}
				this.ReferencedAssemblyNodes = nodes;
			}
		}

		public ICommand NavigateToCommand { get; private set; }

		#region References
		public IEnumerable<TreeNodeViewModel> ReferencedAssemblyNodes { get; private set; }

		private MetadataContainer _metadata;
		private Module _module;

		private Func<ProgressInfo, IEnumerable<TreeNodeViewModel>> CreateTypeItemsSourceDelegate(int typeRefIndex)
		{
			return (progress => this.GetTypeNodes(typeRefIndex));
		}
		private IEnumerable<TreeNodeViewModel> GetTypeNodes(int typeRefIndex)
		{
			int token = CodedIndex.TokenFromTableRow(MetadataTableNumber.TypeRef, typeRefIndex + 1);

			IList<MemberRefRow> memberRefRows = this._metadata.MemberRefTable.Rows;
			SortedList<string, TreeNodeViewModel> nodes = new SortedList<string, TreeNodeViewModel>();
			for (int i = 0; i < memberRefRows.Count; i++)
			{
				MemberRefRow memberRefRow = memberRefRows[i];

				if (memberRefRow.Class.Token == token)
				{
					try
					{
						MemberInfo member = this._module.ResolveMember(CodedIndex.TokenFromTableRow(MetadataTableNumber.MemberRef, i + 1));
						MemberViewInfo viewInfo = MemberViewInfo.GetMemberViewInfo(member);
						TreeNodeViewModel node = new TreeNodeViewModel(
							AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(viewInfo.IconKey),
							UiString.FromString(viewInfo.Heading),
							member
							);
						nodes.Add(viewInfo.Heading, node);
					}
					catch
					{
						// Probably an error in the metadata
					}
				}
			}

			return nodes.Values;
		}
		private IEnumerable<TreeNodeViewModel> GetAssemblyNodes(int assemblyIndex)
		{
			int token = CodedIndex.TokenFromTableRow(MetadataTableNumber.AssemblyRef, assemblyIndex + 1);

			IList<TypeRefRow> typeRefRows = this._metadata.TypeRefTable.Rows;
			SortedList<string, TreeNodeViewModel> nodes = new SortedList<string, TreeNodeViewModel>();
			for (int i = 0; i < typeRefRows.Count; i++)
			{
				TypeRefRow typeRefRow = typeRefRows[i];
				if (typeRefRow.ResolutionScope.Token == token)
				{
					try
					{
						Type type = this._module.ResolveType(CodedIndex.TokenFromTableRow(MetadataTableNumber.TypeRef, i + 1));
						MemberViewInfo viewInfo = MemberViewInfo.GetMemberViewInfo(type);
						TreeNodeViewModel node = new TreeNodeViewModel(
							AssemblyAnalyzerEditor.MetadataIconProvider.GetImage(viewInfo.IconKey),
							UiString.FromString(viewInfo.Heading),
							type
							) { ItemsSourceDelegate = this.CreateTypeItemsSourceDelegate(i) };
						nodes.Add(viewInfo.Heading, node);
					}
					catch
					{
						// Probably an error in the metadata
					}
				}
			}

			return nodes.Values;
		}

		private Func<ProgressInfo, IEnumerable<TreeNodeViewModel>> CreateItemsSourceDelegate(int assemblyIndex)
		{
			return (progress => this.GetAssemblyNodes(assemblyIndex));
		}
		#endregion
	}
}
